/*
 * Copyright (c) 2022, IMMORTA Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 *
 * - Redistributions of source code must retain the above copyright notice, this list
 *   of conditions and the following disclaimer.
 *
 * - Redistributions in binary form must reproduce the above copyright notice, this
 *   list of conditions and the following disclaimer in the documentation and/or
 *   other materials provided with the distribution.
 *
 * - Neither the name of IMMORTA Inc. nor the names of its
 *   contributors may be used to endorse or promote products derived from this
 *   software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/*!
 * @file dma_driver.c
 * @brief This file defines dma driver functions
 */

/*******Includes***************************************************************/
#include <stdlib.h>
#include "dma_reg_access.h"
#include "clock_drv.h"

/*******Definitions************************************************************/

/*******Variables**************************************************************/
/*! @brief Table to save register base addresses of DMA channels instance */
static DMA_CH_Type* const s_dmaChannelRegBase[CONFIG_DMA_CH_COUNT] = CONFIG_DMA_CH_BASE_ADDRS;

/*! @brief Save the registered callback functions by each channel */
static dma_channel_callback_t s_channelCallback[CONFIG_DMA_CH_COUNT] = {{NULL}};

/*! @brief Save the linked list by each channel */
static dma_linked_list_item_t s_cycleChannel[CONFIG_DMA_CH_COUNT];

/*! @brief Table to dma interrupt request ids */
static const IRQn_Type s_dmaIrqId[CONFIG_DMA_CH_COUNT] = CONFIG_DMA_IRQS;

/*! @brief Variable to save dma init status */
static bool s_dmaInitStatus = false;

/*******Prototypes*************************************************************/
static void DMA_ChannelDefaultInit(uint8_t channel);
static void DMA_SetLinkedList(DMA_CH_Type* regBase, const dma_linked_list_item_t* linkedList);

/*******Private Functions******************************************************/

/*******Code*******************************************************************/

/*******************************************************************************
 *** Function Name : DMA_Init
 *** Description   : Initializes the DMA configuration structure
 ******************************************************************************/
void DMA_Init(const dma_channel_config_t* chnConfigArray[], uint8_t chnCount)
{
    ASSERT_PARAM(chnConfigArray != NULL);
    ASSERT_PARAM(chnCount < CONFIG_DMA_CH_COUNT);

    /* Loop initialization DMA channel configuration */
    for (uint8_t index = 0U; index < chnCount; index++) {
        DMA_ChannelInit(chnConfigArray[index]);
    }
}

/*******************************************************************************
 *** Function Name : DMA_DeInit
 *** Description   : Deinitializes the DMA module
 ******************************************************************************/
void DMA_Deinit(void)
{
    /* Reset all channels configuration to reset value */
    for (uint8_t index = 0U; index < CONFIG_DMA_CH_COUNT; index++) {
        DMA_ChannelDeInit(index);
    }

    DMA_REG_SetEnable(DMA, false);

    module_names_t instanceModuleName = MODULE_DMA;
    /* Set soft reset active of dma module */
    CLOCK_SetModuleSoftResetState(instanceModuleName, RESET_ACTIVE);
    /* Disable dma module bus clock */
    CLOCK_SetModuleBusClockEnable(instanceModuleName, false);

    s_dmaInitStatus = false;
}

/*******************************************************************************
 *** Function Name : DMA_ChannelInit
 *** Description   : Initializes the DMA chanel
 ******************************************************************************/
void DMA_ChannelInit(const dma_channel_config_t* channelConfig)
{
    ASSERT_PARAM(channelConfig != NULL);
    uint8_t channel = channelConfig->channel;

    ASSERT_PARAM(channel < CONFIG_DMA_CH_COUNT);
    ASSERT_PARAM(channelConfig->transferSize <= DMA_CHANNEL_TRANFER_MAX_SIZE);


    DMA_ChannelDefaultInit(channel);
    DMA_CH_Type* regBase = s_dmaChannelRegBase[channel];
    DMA_REG_SetTransferType(regBase, channelConfig->transferType);
    DMA_REG_SetLLIITCEnable(regBase, true);
    /* Destination peripheral. This value selects the DMA destination request peripheral.
     *  This field is ignored if the destination of the transfer is to memory. */
    DMA_REG_SetSrcPeriphRequest(regBase, channelConfig->srcPeriphRequest);

    /* Source peripheral. This value selects the DMA source request peripheral.
     * This field is ignored if the source of the transfer is from memory. */
    DMA_REG_SetDestPeriphRequest(regBase, channelConfig->destPeriphRequest);

    DMA_REG_SetSrcAddr(regBase, channelConfig->srcAddress);
    DMA_REG_SetDestAddr(regBase, channelConfig->destAddress);
    DMA_REG_SetSrcAddrIncrement(regBase, channelConfig->srcAddressIncrement);
    DMA_REG_SetDestAddrIncrement(regBase, channelConfig->destAddressIncrement);
    DMA_REG_SetSrcTransferWidth(regBase, channelConfig->srcTransferWidth);
    DMA_REG_SetDestTransferWidth(regBase, channelConfig->destTransferWidth);
    DMA_REG_SetTransferSize(regBase, channelConfig->transferSize);

    DMA_REG_SetCompleteIntEnable(regBase, channelConfig->completeIntEnable);
    DMA_REG_SetErrorIntEnable(regBase, channelConfig->errorIntEnable);
    s_channelCallback[channel].callback = channelConfig->callback;
    s_channelCallback[channel].callbackParam = channelConfig->callbackParam;

    /* Use the linked list to implement the loop transfer */
    if (channelConfig->circularModel) {
        s_cycleChannel[channel].srcAddress = channelConfig->srcAddress;
        s_cycleChannel[channel].destAddress = channelConfig->destAddress;
        s_cycleChannel[channel].control.lliITCEnable = true;
        s_cycleChannel[channel].control.srcAddressIncrement = channelConfig->srcAddressIncrement;
        s_cycleChannel[channel].control.destAddressIncrement = channelConfig->destAddressIncrement;
        s_cycleChannel[channel].control.srcTransferWidth = (uint32_t)(channelConfig->srcTransferWidth);
        s_cycleChannel[channel].control.destTransferWidth = (uint32_t)(channelConfig->destTransferWidth);
        s_cycleChannel[channel].control.transferSize = channelConfig->transferSize;
        /* The next address of the Linked List item points to itself for circular transmission */
        s_cycleChannel[channel].next = &s_cycleChannel[channel];
        /* Set the data block to the LLI register */
        DMA_REG_SetLinkedList(regBase, &s_cycleChannel[channel]);
    }
}

/*******************************************************************************
 *** Function Name : DMA_ChannelDeInit
 *** Description   : Deinitializes the DMA chanel
 ******************************************************************************/
void DMA_ChannelDeInit(uint8_t channel)
{
    DMA_CH_Type* regBase = s_dmaChannelRegBase[channel];
    DMA_REG_ChannelInit(regBase);

    NVIC_DisableIRQ(s_dmaIrqId[channel]);
    NVIC_ClearPendingIRQ(s_dmaIrqId[channel]);
}

/*******************************************************************************
 *** Function Name : DMA_ConfigSingleTransfer
 *** Description   : Configures single data transfer with DMA
 ******************************************************************************/
void DMA_ConfigSingleTransfer(uint8_t channel,
                              dma_transfer_type_t type,
                              uint32_t srcAddr,
                              uint32_t destAddr,
                              dma_transfer_width_t transferWidth,
                              uint32_t transferSize,
                              dma_perial_request_t request)
{
    ASSERT_PARAM(channel < CONFIG_DMA_CH_COUNT);
    ASSERT_PARAM(transferSize <= DMA_CHANNEL_TRANFER_MAX_SIZE);

    DMA_ChannelDefaultInit(channel);
    DMA_CH_Type* regBase = s_dmaChannelRegBase[channel];
    DMA_REG_SetTransferType(regBase, type);
    DMA_REG_SetSrcAddr(regBase, srcAddr);
    DMA_REG_SetDestAddr(regBase, destAddr);
    DMA_REG_SetSrcTransferWidth(regBase, transferWidth);
    DMA_REG_SetDestTransferWidth(regBase, transferWidth);
    DMA_REG_SetLLIITCEnable(regBase, true);

    /* If it is a Memory address, it is incremented by default;
     * if it is a Peripheral address, it is not incremented by default. */
    if (type == DMA_TRANSFER_MEM2MEM) {
        DMA_REG_SetSrcAddrIncrement(regBase, true);
        DMA_REG_SetDestAddrIncrement(regBase, true);
    } else if (type == DMA_TRANSFER_MEM2PERIPH) {
        DMA_REG_SetSrcAddrIncrement(regBase, true);
        DMA_REG_SetDestAddrIncrement(regBase, false);
        DMA_REG_SetDestPeriphRequest(regBase, request);
    } else if (type == DMA_TRANSFER_PERIPH2MEM) {
        DMA_REG_SetSrcAddrIncrement(regBase, false);
        DMA_REG_SetDestAddrIncrement(regBase, true);
        DMA_REG_SetSrcPeriphRequest(regBase, request);
    } else {
        DMA_REG_SetSrcAddrIncrement(regBase, false);
        DMA_REG_SetDestAddrIncrement(regBase, false);
        DMA_REG_SetSrcPeriphRequest(regBase, request);
        DMA_REG_SetDestPeriphRequest(regBase, request);
    }

    DMA_REG_SetTransferSize(regBase, transferSize);
}

/*******************************************************************************
 *** Function Name : DMA_ConfigLoopTransfer
 *** Description   : Configures the DMA transfer in loop mode
 ******************************************************************************/
void DMA_ConfigLoopTransfer(uint8_t channel,
                            dma_transfer_type_t type,
                            uint32_t srcAddr,
                            uint32_t destAddr,
                            dma_transfer_width_t transferWidth,
                            uint32_t transferSize,
                            dma_perial_request_t request)
{
    ASSERT_PARAM(channel < CONFIG_DMA_CH_COUNT);
    ASSERT_PARAM(transferSize <= DMA_CHANNEL_TRANFER_MAX_SIZE);

    DMA_ChannelDefaultInit(channel);
    DMA_CH_Type* regBase = s_dmaChannelRegBase[channel];
    DMA_REG_SetTransferType(regBase, type);

    /* Use the linked list to implement the loop transfer */
    s_cycleChannel[channel].srcAddress = srcAddr;
    s_cycleChannel[channel].destAddress = destAddr;
    s_cycleChannel[channel].control.lliITCEnable = true;
    s_cycleChannel[channel].control.transferSize = transferSize;
    s_cycleChannel[channel].control.srcTransferWidth = (uint32_t)transferWidth;
    s_cycleChannel[channel].control.destTransferWidth = (uint32_t)transferWidth;

    /* If it is a Memory address, it is incremented by default;
     * if it is a Peripheral address, it is not incremented by default. */
    if (type == DMA_TRANSFER_MEM2MEM) {
        s_cycleChannel[channel].control.srcAddressIncrement = true;
        s_cycleChannel[channel].control.destAddressIncrement = true;
    } else if (type == DMA_TRANSFER_MEM2PERIPH) {
        s_cycleChannel[channel].control.srcAddressIncrement = true;
        s_cycleChannel[channel].control.destAddressIncrement = false;
        DMA_REG_SetDestPeriphRequest(regBase, request);
    } else if (type == DMA_TRANSFER_PERIPH2MEM) {
        s_cycleChannel[channel].control.srcAddressIncrement = false;
        s_cycleChannel[channel].control.destAddressIncrement = true;
        DMA_REG_SetSrcPeriphRequest(regBase, request);
    } else {
        s_cycleChannel[channel].control.srcAddressIncrement = false;
        s_cycleChannel[channel].control.destAddressIncrement = false;
        DMA_REG_SetSrcPeriphRequest(regBase, request);
        DMA_REG_SetDestPeriphRequest(regBase, request);
    }

    /* The next address of the Linked List item points to itself for circular transmission */
    s_cycleChannel[channel].next = &s_cycleChannel[channel];
    /* Set the data block to the LLI register */
    DMA_SetLinkedList(regBase, &s_cycleChannel[channel]);
}

/*******************************************************************************
 *** Function Name : DMA_ConfigLoopTransfer
 *** Description   : Configures linked list data transfer with DMA
 ******************************************************************************/
void DMA_ConfigLinkedListTransfer(uint8_t channel,
                                  dma_transfer_type_t type,
                                  dma_perial_request_t request,
                                  const dma_linked_list_item_t* linkedList)
{
    ASSERT_PARAM(channel < CONFIG_DMA_CH_COUNT);
    ASSERT_PARAM(linkedList != NULL);

    DMA_CH_Type* regBase = s_dmaChannelRegBase[channel];

    DMA_ChannelDefaultInit(channel);
    DMA_REG_SetTransferType(regBase, type);
    DMA_REG_SetSrcPeriphRequest(regBase, request);
    DMA_REG_SetDestPeriphRequest(regBase, request);
    DMA_SetLinkedList(regBase, linkedList);
}

/*******************************************************************************
 *** Function Name : DMA_InstallCallback
 *** Description   : Registers the callback function and the parameter for DMA channel
 ******************************************************************************/
void DMA_InstallCallback(uint8_t channel, dma_callback_t callback, void* param)
{
    ASSERT_PARAM(channel < CONFIG_DMA_CH_COUNT);
    ASSERT_PARAM(callback != NULL);

    s_channelCallback[channel].callback = callback;
    s_channelCallback[channel].callbackParam = param;
}

/*******************************************************************************
 *** Function Name : DMA_ConfigureInt
 *** Description   : Disables/Enables the channel interrupt requests
 ******************************************************************************/
void DMA_ConfigureInt(uint8_t channel,
                      dma_channel_int_t intType,
                      bool enable)
{
    ASSERT_PARAM(channel < CONFIG_DMA_CH_COUNT);

    DMA_CH_Type* regBase = s_dmaChannelRegBase[channel];

    if ((uint32_t)intType & (uint32_t)DMA_CHN_COMPLETE_INT) {
        DMA_REG_SetCompleteIntEnable(regBase, enable);
    }

    if ((uint32_t)intType & (uint32_t)DMA_CHN_ERROR_INT) {
        DMA_REG_SetErrorIntEnable(regBase, enable);
    }
}

/*******************************************************************************
 *** Function Name : DMA_StartChannel
 *** Description   : Start the DMA channel, this function enable DMA channel
 ******************************************************************************/
void DMA_StartChannel(uint8_t channel)
{
    ASSERT_PARAM(channel < CONFIG_DMA_CH_COUNT);

    DMA_CH_Type* regBase = s_dmaChannelRegBase[channel];

    DMA_REG_SetEnable(DMA, true);
    DMA_REG_SetChannleEnable(regBase, true);
}

/*******************************************************************************
 *** Function Name : DMA_StopChannel
 *** Description   : Stop the DMA channel, this function disable DMA channel
 ******************************************************************************/
void DMA_StopChannel(uint8_t channel)
{
    ASSERT_PARAM(channel < CONFIG_DMA_CH_COUNT);

    DMA_CH_Type* regBase = s_dmaChannelRegBase[channel];

    DMA_REG_SetChannleEnable(regBase, false);
}

/*******************************************************************************
 *** Function Name : DMA_RestartChannel
 *** Description   : When the transmission is completed, reconfigure the address and tranferSize to transmit again
 ******************************************************************************/
void DMA_RestartChannel(uint8_t channel,
                        uint32_t srcAddr,
                        uint32_t destAddr,
                        uint32_t transferSize)
{
    ASSERT_PARAM(channel < CONFIG_DMA_CH_COUNT);

    DMA_CH_Type* regBase = s_dmaChannelRegBase[channel];

    DMA_ERG_ClearCompletedIntStatus(regBase);
    DMA_ERG_ClearErrorIntStatus(regBase);

    DMA_REG_SetTransferSize(regBase, transferSize);
    DMA_REG_SetSrcAddr(regBase, srcAddr);
    DMA_REG_SetDestAddr(regBase, destAddr);

    DMA_StartChannel(channel);
}

/*******************************************************************************
 *** Function Name : DMA_SetChannelHalt
 *** Description   : Set DMA tranmission pause or continue
 ******************************************************************************/
void DMA_SetChannelHalt(uint8_t channel, bool halt)
{
    ASSERT_PARAM(channel < CONFIG_DMA_CH_COUNT);

    DMA_CH_Type* regBase = s_dmaChannelRegBase[channel];

    DMA_REG_SetHaltEnable(regBase, halt);
}

/*******************************************************************************
 *** Function Name : DMA_GetRemainingCount
 *** Description   : Get DMA channel remain transfered data num
 ******************************************************************************/
uint32_t DMA_GetRemainingCount(uint8_t channel)
{
    ASSERT_PARAM(channel < CONFIG_DMA_CH_COUNT);

    DMA_CH_Type* regBase = s_dmaChannelRegBase[channel];

    return DMA_REG_GetTransferSize(regBase);
}

/*******************************************************************************
 *** Function Name : DMA_GetChannelStatus
 *** Description   : Get DMA channel transfered status(normal/complete/error)
 ******************************************************************************/
dma_channel_status_t DMA_GetChannelStatus(uint8_t channel)
{
    ASSERT_PARAM(channel < CONFIG_DMA_CH_COUNT);

    DMA_CH_Type* regBase = s_dmaChannelRegBase[channel];
    dma_channel_status_t status = DMA_STATUS_NORMAL;

    if (DMA_ERG_IsTransferError(regBase)) {
        status = DMA_STATUS_ERROR;
    } else if (DMA_ERG_IsTransferCompleted(regBase)) {
        status = DMA_STATUS_COMPLETE;
    }

    return status;
}

/*******************************************************************************
 *** Function Name : DMA_TriggerSwRequest
 *** Description   : Triggers a sw request for the current channel
 ******************************************************************************/
void DMA_TriggerSwRequest(dma_perial_request_t request)
{
    DMA_REG_TriggerSwRequest(DMA, request);
}

/*******************************************************************************
 *** Function Name : DMA_IRQHandlerProcess
 *** Description   : DMA channel interrupt handler process
 ******************************************************************************/
void DMA_IRQHandlerProcess(uint8_t channel)
{
    dma_channel_callback_t channelCallback = s_channelCallback[channel];
    DMA_CH_Type* regBase = s_dmaChannelRegBase[channel];

    if (DMA_REG_IsCompleteIntEnabled(regBase) && DMA_ERG_IsTransferCompleted(regBase)) {
        /* Clear transfer complete status flag */
        DMA_ERG_ClearCompletedIntStatus(regBase);

        if (channelCallback.callback != NULL) {
            /* Callback transfer completion event */
            channelCallback.callback(channel, DMA_EVENT_TRANFER_COMPLETE, channelCallback.callbackParam);
        }
    }

    if (DMA_REG_IsErrorIntEnabled(regBase) && DMA_ERG_IsTransferError(regBase)) {
        /* Clear transfer error status flag */
        DMA_ERG_ClearErrorIntStatus(regBase);

        if (channelCallback.callback != NULL) {
            /* Callback transmission error event */
            channelCallback.callback(channel, DMA_EVENT_TRANFER_ERROR, channelCallback.callbackParam);
        }
    }
}

/*******************************************************************************
 *** Function Name : DMA_ChannelDefaultInit
 *** Description   : Initializes the DMA channel default configuration
 ******************************************************************************/
static void DMA_ChannelDefaultInit(uint8_t channel)
{
    module_names_t instanceModuleName = MODULE_DMA;
    DMA_CH_Type* regBase = s_dmaChannelRegBase[channel];

    if (s_dmaInitStatus == false) {
        /* Re-Enable dma module bus clock */
        CLOCK_SetModuleBusClockEnable(instanceModuleName, true);
        /* Set soft reset inactive of dma module */
        CLOCK_SetModuleSoftResetState(instanceModuleName, RESET_INACTIVE);
        s_dmaInitStatus = true;
    } else {
        /* Do nothing. */
    }

    NVIC_EnableIRQ(s_dmaIrqId[channel]);
    DMA_REG_ChannelInit(regBase);
}

/*******************************************************************************
 *** Function Name : DMA_SetLinkedList
 *** Description   : Set DMA channel linked list
 ******************************************************************************/
static void DMA_SetLinkedList(DMA_CH_Type* regBase, const dma_linked_list_item_t* linkedList)
{
    DMA_REG_SetSrcAddr(regBase, linkedList->srcAddress);
    DMA_REG_SetDestAddr(regBase, linkedList->destAddress);
    DMA_REG_SetSrcAddrIncrement(regBase, linkedList->control.srcAddressIncrement);
    DMA_REG_SetDestAddrIncrement(regBase, linkedList->control.destAddressIncrement);
    DMA_REG_SetSrcTransferWidth(regBase, (dma_transfer_width_t)(linkedList->control.srcTransferWidth));
    DMA_REG_SetDestTransferWidth(regBase, (dma_transfer_width_t)(linkedList->control.destTransferWidth));
    DMA_REG_SetTransferSize(regBase, linkedList->control.transferSize);
    DMA_REG_SetLLIITCEnable(regBase, linkedList->control.lliITCEnable);
    DMA_REG_SetLinkedList(regBase, linkedList->next);
}

/*******EOF********************************************************************/
